﻿/*
Copyright (c) 2009 Vratislav Cermak (vratacermak@gmail.com)
 
This file is part of TeachMe Project. See http://code.google.com/p/teach-me for more information.
 
TeachMe is an open source project. Using of TeachMe is free of charge.
It is distributed under GNU General Public License version 3 (GPLv3).
GPLv3 can be found at http://www.gnu.org/licenses/gpl.html.
 
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

using System;
using System.Collections.Generic;
using System.Drawing;
using TeachMe.Application.Configuration;
using TeachMe.Application.Themes;

namespace TeachMe.Application.Drawing
{
    public class DrawContextBuilder
    {
        private readonly Dictionary<RegionType, Rectangle> _backgroundRectangles = new Dictionary<RegionType, Rectangle>();
        private readonly Theme _theme;
        private readonly Graphics _graphics;

        public DrawContextBuilder(Theme theme, Graphics graphics)
        {
            _theme = theme;
            _graphics = graphics;

            _backgroundRectangles = new Dictionary<RegionType, Rectangle>();
            _backgroundRectangles.Add(RegionType.Top, Theme.Background.GetRegion(RegionType.Top).Rectangle);
            _backgroundRectangles.Add(RegionType.Middle, Theme.Background.GetRegion(RegionType.Middle).Rectangle);
            _backgroundRectangles.Add(RegionType.Bottom, Theme.Background.GetRegion(RegionType.Bottom).Rectangle);
            _backgroundRectangles.Add(RegionType.WorkingArea, Theme.Background.GetRegion(RegionType.WorkingArea).Rectangle);
        }

        public Theme Theme
        {
            get { return _theme; }
        }

        public int Magnification { get; set; }

        private int GetNumberOfMultipliers(int heightToAdd)
        {
            int extraMultipliersCount = (heightToAdd / _backgroundRectangles[RegionType.Middle].Height);

            if (heightToAdd % _backgroundRectangles[RegionType.Middle].Height > 0)
            {
                extraMultipliersCount++;
            }

            return extraMultipliersCount;
        }

        public EntryDrawingContext CreateEntryDrawingContext(EntryDescriptor descriptor)
        {
            EntryDrawingContext result = new EntryDrawingContext();

            int nextTop = _backgroundRectangles[RegionType.WorkingArea].Top;

            // texts

            result.TextDrawingContexts = new Dictionary<EntryTextType, TextDrawingContext>();

            result.TextDrawingContexts.Add(EntryTextType.From, CreateTextDrawingContext(descriptor.From, ref nextTop, Theme.GetText(EntryTextType.From), Magnification));
            
            nextTop += Theme.TextsVerticalDistance;
            result.TextDrawingContexts.Add(EntryTextType.To, CreateTextDrawingContext(descriptor.To, ref nextTop, Theme.GetText(EntryTextType.To), Magnification));

            if (!String.IsNullOrEmpty(descriptor.Example))
            {
                nextTop += Theme.TextsVerticalDistance;
                result.TextDrawingContexts.Add(EntryTextType.Example, CreateTextDrawingContext(descriptor.Example, descriptor.RealFrom, ref nextTop, Theme.GetText(EntryTextType.Example), Magnification));
            }

            int heightOfTexts = nextTop - _backgroundRectangles[RegionType.WorkingArea].Top;

            // background

            result.BackgroundDrawingContext = CreateBackgroundDrawingContext(heightOfTexts - _backgroundRectangles[RegionType.WorkingArea].Height);

            // progress

            int addedHeight = result.BackgroundDrawingContext.Image.Height - Theme.Background.Image.Height;

            result.ProgressDrawingContext = CreateProgress(addedHeight);

            // buttons

            result.ButtonDrawingContexts = new Dictionary<EntryButtonType, ButtonDrawingContext>();

            foreach (Button button in Theme.Buttons)
            {
                result.ButtonDrawingContexts.Add(button.Type, CreateButtonDrawingContext(button));
            }

            return result;
        }

        private ProgressDrawingContext CreateProgress(int addedHeight)
        {
            ProgressDrawingContext result = new ProgressDrawingContext();

            result.Image = Theme.Progress.Image;
            result.MiddleFragmentStart = Theme.Progress.GetRegion(RegionType.Middle).Rectangle.Left;
            result.RightFragmentStart = Theme.Progress.GetRegion(RegionType.Right).Rectangle.Left;
            result.Rectangle = Theme.Progress.GetRegion(RegionType.ProgressArea).Rectangle;
            result.ExtendMode = Theme.Progress.ExtendMode;

            switch (Theme.Progress.Align)
            {
                case ProgressAlign.Top:
                    break;
                case ProgressAlign.Bottom:
                    result.Rectangle = new Rectangle(result.Rectangle.X, result.Rectangle.Y + addedHeight, result.Rectangle.Width, result.Rectangle.Height);
                    result.Rectangle.Offset(0, addedHeight);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            int progressAreaWidth = result.Rectangle.Width;

            switch (Theme.Progress.ExtendMode)
            {
                case ImageExtendMode.Stretch:
                    result.Segment = (float)(progressAreaWidth - Theme.Progress.Image.Width) / 100;
                    break;
                case ImageExtendMode.Multiply:
                    int middlePartWidth = progressAreaWidth - Theme.Progress.GetRegion(RegionType.Left).Rectangle.Width - Theme.Progress.GetRegion(RegionType.Right).Rectangle.Width;
                    result.Segment = ((float)middlePartWidth / Theme.Progress.GetRegion(RegionType.Middle).Rectangle.Width) / 100;
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            return result;
        }

        private BackgroundDrawingContext CreateBackgroundDrawingContext(int heightToAdd)
        {
            int middlePartHeight;

            if (heightToAdd > 0)
            {
                switch (Theme.Background.ExtendMode)
                {
                    case ImageExtendMode.Stretch:
                        middlePartHeight = _backgroundRectangles[RegionType.Middle].Height + heightToAdd;
                        break;
                    case ImageExtendMode.Multiply:
                        middlePartHeight = _backgroundRectangles[RegionType.Middle].Height + (GetNumberOfMultipliers(heightToAdd) * _backgroundRectangles[RegionType.Middle].Height);
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }
            else
            {
                middlePartHeight = _backgroundRectangles[RegionType.Middle].Height;
            }

            int backgroundHeight = _backgroundRectangles[RegionType.Top].Height + middlePartHeight + _backgroundRectangles[RegionType.Bottom].Height;

            BackgroundDrawingContext result = new BackgroundDrawingContext();

            result.Image = new Bitmap(Theme.Background.Image.Width, backgroundHeight);

            using (Graphics backgroundGraphics = Graphics.FromImage(result.Image))
            {
                int nextTop = 0;
                
                Rectangle destination = new Rectangle(0, nextTop, _backgroundRectangles[RegionType.Top].Width, _backgroundRectangles[RegionType.Top].Height);

                backgroundGraphics.DrawImage(Theme.Background.Image, destination, _backgroundRectangles[RegionType.Top], GraphicsUnit.Pixel);

                nextTop += _backgroundRectangles[RegionType.Top].Height;

                if (heightToAdd > 0)
                {
                    switch (Theme.Background.ExtendMode)
                    {
                        case ImageExtendMode.Stretch:
                            destination = new Rectangle(0, nextTop, _backgroundRectangles[RegionType.Middle].Width, middlePartHeight);
                            backgroundGraphics.DrawImage(Theme.Background.Image, destination, _backgroundRectangles[RegionType.Middle], GraphicsUnit.Pixel);
                            nextTop += middlePartHeight;
                            break;
                        case ImageExtendMode.Multiply:
                            for (int idx = 0; idx < GetNumberOfMultipliers(heightToAdd) + 1; idx++)
                            {
                                destination = new Rectangle(new Point(0, nextTop), _backgroundRectangles[RegionType.Middle].Size);
                                backgroundGraphics.DrawImage(Theme.Background.Image, destination, _backgroundRectangles[RegionType.Middle], GraphicsUnit.Pixel);
                                nextTop += _backgroundRectangles[RegionType.Middle].Height;
                            }
                            break;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }
                }
                else
                {
                    destination = new Rectangle(0, nextTop, _backgroundRectangles[RegionType.Middle].Width, middlePartHeight);
                    backgroundGraphics.DrawImage(Theme.Background.Image, destination, _backgroundRectangles[RegionType.Middle], GraphicsUnit.Pixel);
                    nextTop += middlePartHeight;
                }

                destination = new Rectangle(0, nextTop, _backgroundRectangles[RegionType.Bottom].Width, _backgroundRectangles[RegionType.Bottom].Height);

                backgroundGraphics.DrawImage(Theme.Background.Image, destination, _backgroundRectangles[RegionType.Bottom], GraphicsUnit.Pixel);
            }

            return result;
        }

        private static ButtonDrawingContext CreateButtonDrawingContext(Button themedButton)
        {
            ButtonDrawingContext result = new ButtonDrawingContext();

            result.Normal = themedButton.GetImage(ButtonImageType.Normal);
            result.Hover = themedButton.GetImage(ButtonImageType.Hover);
            result.Pressed = themedButton.GetImage(ButtonImageType.Pressed);
            result.Down = themedButton.GetImage(ButtonImageType.Down);
            result.Rectangle = themedButton.Rectangle;

            return result;
        }

        private TextDrawingContext CreateTextDrawingContext(string text, ref int top, Text themedText, int magnification)
        {
            return CreateTextDrawingContext(text, null, ref top, themedText, magnification);
        }

        // TODO: for some reason i need to add '1' to width to make last letter displayed
        private TextDrawingContext CreateTextDrawingContext(string text, string highlightText, ref int top, Text themedText, int magnification)
        {
            var font = magnification == 0 ? themedText.Font : MagnifyFont(themedText.Font, magnification);
            var result = new TextDrawingContext();
            var measures = _graphics.MeasureString(text, font, _backgroundRectangles[RegionType.WorkingArea].Width);
            
            result.Rectangle = new Rectangle(_backgroundRectangles[RegionType.WorkingArea].Left, top, (int)measures.Width + 1, (int)measures.Height);
            result.Brush = new SolidBrush(themedText.Color);
            result.HighlightBrush = themedText.HasHighlightColor ? new SolidBrush(themedText.HighlightColor) : new SolidBrush(Invert(themedText.Color));
            result.HighlightText = highlightText;
            result.Font = font;
            result.Text = text;

            top += (int)measures.Height;

            return result;
        }

        private static Font MagnifyFont(Font font, int magnification)
        {
            int newSize = (int)(font.Size + (font.Size / 100f) * magnification);
            Logging.Debug(typeof(DrawContextBuilder), "Font size after magnification {0}% is '{1}'.", magnification, newSize);
            return ObjectFactoryWrappers.CreateFont(font.FontFamily, newSize, font.Style);
        }

        public Color Invert(Color input)
        {
            return Color.FromArgb(input.A, (input.R + 128) % 256, (input.G + 128) % 256, (input.B + 128) % 256);
        }
    }
}
